using System;
using ch.ethz.ssh2.log;
using System.Threading;
using ch.ethz.ssh2.util;
namespace ch.ethz.ssh2.channel
{

    /// <summary> RemoteX11AcceptThread.
    /// 
    /// </summary>
    /// <author>  Christian Plattner
    /// </author>
    /// <version>  2.50, 03/15/10
    /// </version>
    public class RemoteX11AcceptThread
    {
        //UPGRADE_NOTE: Final was removed from the declaration of 'log '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
        //UPGRADE_NOTE: The initialization of  'log' was moved to static method 'ch.ethz.ssh2.channel.RemoteX11AcceptThread'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
        private static readonly Logger log;

        internal Channel c;

        internal System.String remoteOriginatorAddress;
        internal int remoteOriginatorPort;

        internal System.Net.Sockets.TcpClient client;

        public RemoteX11AcceptThread(Channel c, System.String remoteOriginatorAddress, int remoteOriginatorPort)
        {
            this.c = c;
            this.remoteOriginatorAddress = remoteOriginatorAddress;
            this.remoteOriginatorPort = remoteOriginatorPort;
        }

        public void Run()
        {
            try
            {
                /* Send Open Confirmation */

                c.cm.sendOpenConfirmation(c);

                /* Read startup packet from client */

                System.IO.Stream remote_os = c.StdinStream;
                System.IO.Stream remote_is = c.StdoutStream;

                /* The following code is based on the protocol description given in:
                * Scheifler/Gettys,
                * X Windows System: Core and Extension Protocols:
                * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X
                * (from the ETH library - after being here for almost ten
                * years one of the few books I borrowed... sad but true =)
                */

                /*
                * Client startup:
                * 
                * 1 0X42 MSB first/0x6c lSB first - byteorder
                * 1 - unused
                * 2 card16 - protocol-major-version
                * 2 card16 - protocol-minor-version
                * 2 n - lenght of authorization-protocol-name
                * 2 d - lenght of authorization-protocol-data
                * 2 - unused
                * string8 - authorization-protocol-name
                * p - unused, p=pad(n)
                * string8 - authorization-protocol-data
                * q - unused, q=pad(d)
                * 
                * pad(X) = (4 - (X mod 4)) mod 4
                * 
                * Server response:
                * 
                * 1 (0 failed, 2 authenticate, 1 success)
                * ...
                * 
                */

                /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */

                byte[] header = new byte[6];

                if (((remote_is is ch.ethz.ssh2.StreamGobbler || remote_is is ch.ethz.ssh2.channel.ChannelInputStream) ? (int)Utility.InvokeMethodAsVirtual(remote_is, "read", new System.Object[] { header }) : Utility.ReadInput(remote_is, header, 0, header.Length)) != 6)
                    throw new System.IO.IOException("Unexpected EOF on X11 startup!");

                if ((header[0] != 0x42) && (header[0] != 0x6c))
                    // 0x42 MSB first, 0x6C LSB first
                    throw new System.IO.IOException("Unknown endian format in X11 message!");

                /* Yes, I came up with this myself - shall I file an application for a patent? =) */

                int idxMSB = (header[0] == 0x42) ? 0 : 1;

                /* Read authorization data header */

                byte[] auth_buff = new byte[6];

                if (((remote_is is ch.ethz.ssh2.StreamGobbler || remote_is is ch.ethz.ssh2.channel.ChannelInputStream) ? (int)Utility.InvokeMethodAsVirtual(remote_is, "read", new System.Object[] { auth_buff }) : Utility.ReadInput(remote_is, auth_buff, 0, auth_buff.Length)) != 6)
                    throw new System.IO.IOException("Unexpected EOF on X11 startup!");

                int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff);
                int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff);

                if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256))
                    throw new System.IO.IOException("Buggy X11 authorization data");

                int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4);
                int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4);

                byte[] authProtocolName = new byte[authProtocolNameLength];
                byte[] authProtocolData = new byte[authProtocolDataLength];

                byte[] paddingBuffer = new byte[4];

                if (((remote_is is ch.ethz.ssh2.StreamGobbler || remote_is is ch.ethz.ssh2.channel.ChannelInputStream) ? (int)Utility.InvokeMethodAsVirtual(remote_is, "read", new System.Object[] { authProtocolName }) : Utility.ReadInput(remote_is, authProtocolName, 0, authProtocolName.Length)) != authProtocolNameLength)
                    throw new System.IO.IOException("Unexpected EOF on X11 startup! (authProtocolName)");

                if (((remote_is is ch.ethz.ssh2.StreamGobbler || remote_is is ch.ethz.ssh2.channel.ChannelInputStream) ? (int)Utility.InvokeMethodAsVirtual(remote_is, "read", new System.Object[] { paddingBuffer, 0, authProtocolNamePadding }) : Utility.ReadInput(remote_is, paddingBuffer, 0, authProtocolNamePadding)) != authProtocolNamePadding)
                    throw new System.IO.IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)");

                if (((remote_is is ch.ethz.ssh2.StreamGobbler || remote_is is ch.ethz.ssh2.channel.ChannelInputStream) ? (int)Utility.InvokeMethodAsVirtual(remote_is, "read", new System.Object[] { authProtocolData }) : Utility.ReadInput(remote_is, authProtocolData, 0, authProtocolData.Length)) != authProtocolDataLength)
                    throw new System.IO.IOException("Unexpected EOF on X11 startup! (authProtocolData)");

                if (((remote_is is ch.ethz.ssh2.StreamGobbler || remote_is is ch.ethz.ssh2.channel.ChannelInputStream) ? (int)Utility.InvokeMethodAsVirtual(remote_is, "read", new System.Object[] { paddingBuffer, 0, authProtocolDataPadding }) : Utility.ReadInput(remote_is, paddingBuffer, 0, authProtocolDataPadding)) != authProtocolDataPadding)
                    throw new System.IO.IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)");

                if ("MIT-MAGIC-COOKIE-1".Equals(StringEncoder.GetString(authProtocolName)) == false)
                    throw new System.IO.IOException("Unknown X11 authorization protocol!");

                if (authProtocolDataLength != 16)
                    throw new System.IO.IOException("Wrong data length for X11 authorization data!");

                System.Text.StringBuilder tmp = new System.Text.StringBuilder(32);
                for (int i = 0; i < authProtocolData.Length; i++)
                {
                    System.String digit2 = System.Convert.ToString(authProtocolData[i] & 0xff, 16);
                    tmp.Append((digit2.Length == 2) ? digit2 : "0" + digit2);
                }
                System.String hexEncodedFakeCookie = tmp.ToString();

                /* Order is very important here - it may be that a certain x11 forwarding
                * gets disabled right in the moment when we check and register our connection
                * */

                lock (c)
                {
                    /* Please read the comment in Channel.java */
                    c.hexX11FakeCookie = hexEncodedFakeCookie;
                }

                /* Now check our fake cookie directory to see if we produced this cookie */

                X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie);

                if (sd == null)
                    throw new System.IO.IOException("Invalid X11 cookie received.");

                /* If the session which corresponds to this cookie is closed then we will
                * detect this: the session's close code will close all channels
                * with the session's assigned x11 fake cookie.
                */

                client = new System.Net.Sockets.TcpClient(sd.hostname, sd.port);

                System.IO.Stream x11_os = client.GetStream();
                System.IO.Stream x11_is = client.GetStream();

                /* Now we are sending the startup packet to the real X11 server */

                if (x11_os is ch.ethz.ssh2.channel.ChannelOutputStream)
                    ((ch.ethz.ssh2.channel.ChannelOutputStream)x11_os).write(header);
                else
                {
                    byte[] temp_byteArray;
                    temp_byteArray = header;
                    x11_os.Write(temp_byteArray, 0, temp_byteArray.Length);
                }

                if (sd.x11_magic_cookie == null)
                {
                    byte[] emptyAuthData = new byte[6];
                    /* empty auth data, hopefully you are connecting to localhost =) */
                    if (x11_os is ch.ethz.ssh2.channel.ChannelOutputStream)
                        ((ch.ethz.ssh2.channel.ChannelOutputStream)x11_os).write(emptyAuthData);
                    else
                    {
                        byte[] temp_byteArray2;
                        temp_byteArray2 = emptyAuthData;
                        x11_os.Write(temp_byteArray2, 0, temp_byteArray2.Length);
                    }
                }
                else
                {
                    if (sd.x11_magic_cookie.Length != 16)
                        throw new System.IO.IOException("The real X11 cookie has an invalid length!");

                    /* send X11 cookie specified by client */
                    if (x11_os is ch.ethz.ssh2.channel.ChannelOutputStream)
                        ((ch.ethz.ssh2.channel.ChannelOutputStream)x11_os).write(auth_buff);
                    else
                    {
                        byte[] temp_byteArray3;
                        temp_byteArray3 = auth_buff;
                        x11_os.Write(temp_byteArray3, 0, temp_byteArray3.Length);
                    }
                    if (x11_os is ch.ethz.ssh2.channel.ChannelOutputStream)
                        ((ch.ethz.ssh2.channel.ChannelOutputStream)x11_os).write(authProtocolName);
                    else
                    {
                        byte[] temp_byteArray4;
                        temp_byteArray4 = authProtocolName;
                        x11_os.Write(temp_byteArray4, 0, temp_byteArray4.Length);
                    } /* re-use */
                    x11_os.Write(paddingBuffer, 0, authProtocolNamePadding);
                    if (x11_os is ch.ethz.ssh2.channel.ChannelOutputStream)
                        ((ch.ethz.ssh2.channel.ChannelOutputStream)x11_os).write(sd.x11_magic_cookie);
                    else
                    {
                        byte[] temp_byteArray5;
                        temp_byteArray5 = sd.x11_magic_cookie;
                        x11_os.Write(temp_byteArray5, 0, temp_byteArray5.Length);
                    }
                    x11_os.Write(paddingBuffer, 0, authProtocolDataPadding);
                }

                x11_os.Flush();

                /* Start forwarding traffic */

                Thread r2l = new Thread(new ThreadStart(new StreamForwarder(c, null, null, remote_is, x11_os, "RemoteToX11").Run));
                StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote");

                /* No need to start two threads, one can be executed in the current thread */

                r2l.IsBackground = true;
                r2l.Start();
                l2r.Run();

                while (r2l.IsAlive)
                {
                    try
                    {
                        r2l.Join();
                    }
                    catch (System.Threading.ThreadInterruptedException e)
                    {
                        Thread.CurrentThread.Interrupt();
                    }
                }

                /* If the channel is already closed, then this is a no-op */

                c.cm.closeChannel(c, "EOF on both X11 streams reached.", true);
                client.Close();
            }
            catch (System.IO.IOException e)
            {
                //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.getMessage' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                log.log(50, "IOException in X11 proxy code: " + e.Message);

                try
                {
                    //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.getMessage' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                    c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.Message + ")", true);
                }
                catch (System.IO.IOException e1)
                {
                }
                try
                {
                    if (client != null)
                        client.Close();
                }
                catch (System.IO.IOException e1)
                {
                }
            }
        }
        static RemoteX11AcceptThread()
        {
            log = Logger.getLogger(typeof(RemoteX11AcceptThread));
        }
    }
}